package com.lauor.smpedr.core.handler.provider;

import com.lauor.smpedr.Configuration;
import com.lauor.smpedr.core.anno.Id;
import com.lauor.smpedr.core.handler.ResultSetHandler;
import com.lauor.smpedr.core.handler.TypeHandler;
import com.lauor.smpedr.core.helper.EdrHelper;
import com.lauor.smpedr.core.helper.OrmHelper;
import com.lauor.smpedr.exceptions.BuildResultException;
import com.lauor.smpedr.utils.Str;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.*;

public class DefaultResultSetHandler implements ResultSetHandler {

    private final Logger LOG = LoggerFactory.getLogger(DefaultResultSetHandler.class);

    private Configuration configuration;

    public DefaultResultSetHandler(Configuration configuration) {
        this.configuration = configuration;
    }

    public <E> List<E> handleResultSetsForMany(ResultSet resultSet, Class<E> clazz) throws SQLException {
        if ( !resultSet.next() ) return Collections.EMPTY_LIST;

        //属性与对应的setter方法,去除sql忽略的属性
        Map<String, Method> methodMap = this.getFieldSetterMethodMap(clazz);
        //数据库字段名与对象字段名映射关系
        Map<String, String> fieldNameMap = Collections.EMPTY_MAP;
        if ( !methodMap.isEmpty() ){
            fieldNameMap = EdrHelper.getFieldDbRelateEntity(clazz);
        }

        List<E> resultList = new ArrayList<>();
        try {
            do {
                E instance = this.generateRsObj(clazz, resultSet, methodMap, fieldNameMap);
                resultList.add(instance);
            } while ( resultSet.next() );

        } catch (Exception ex){
            LOG.error("Error building results from database", ex);
            throw new BuildResultException("Error building results from database", ex);
        }
        return resultList;
    }

    @Override
    public <E> E handleResultSetsForSingle(ResultSet resultSet, Class<E> clazz) throws SQLException {
        if ( !resultSet.next() ) return null;

        //属性与对应的setter方法,去除sql忽略的属性
        Map<String, Method> methodMap = this.getFieldSetterMethodMap(clazz);
        //数据库字段名与对象字段名映射关系
        Map<String, String> fieldNameMap = Collections.EMPTY_MAP;
        if ( !methodMap.isEmpty() ){
            fieldNameMap = EdrHelper.getFieldDbRelateEntity(clazz);
        }
        try {
            return this.generateRsObj(clazz, resultSet, methodMap, fieldNameMap);
        } catch (Exception ex) {
            LOG.error("Building result from database Error", ex);
            throw new BuildResultException("Building result from database Error", ex);
        }
    }

    private <E> E generateRsObj(Class<E> clazz, ResultSet resultSet, Map<String, Method> methodMap, Map<String, String> fieldNameMap) throws Exception{
        ResultSetMetaData metaData = resultSet.getMetaData();
        final int columnCount = metaData.getColumnCount();
        if (columnCount == 1){
            E instance = null;
            try {
                //尝试带参构造方法构建
                Constructor<E> constructor = clazz.getDeclaredConstructor(clazz);
                Object javaObj = configuration.getTypeHandler().jdbcToJavaBeanType(resultSet, 1, clazz);
                instance = constructor.newInstance(javaObj);
            } catch (Exception ex){
                //尝试直接使用列类型对象
                try {
                    instance = (E) configuration.getTypeHandler().jdbcToJavaBeanType(resultSet, 1, clazz);
                } catch (Exception innerEx){}
            }
            if (instance == null){
                instance = clazz.getDeclaredConstructor().newInstance();
            }
            //再走一遍javaBean逻辑，尽最大努力避免出错
            return this.generateRsByJavaBean(instance, resultSet, methodMap, fieldNameMap);
        }
        E instance = clazz.getDeclaredConstructor().newInstance();
        return this.generateRsByJavaBean(instance, resultSet, methodMap, fieldNameMap);
    }

    @Override
    public <E> List<E> handleGeneratedKeys(ResultSet resultSet, List<E> dataList, Class cls) throws SQLException {
        if ( dataList == null || dataList.isEmpty() || !resultSet.next() ) return dataList;
        //寻找自增列
        Set<String> fieldSet = EdrHelper.findFields(cls, field -> {
            if ( !field.isAnnotationPresent(Id.class) ) return false;

            Id idAnt = field.getAnnotation(Id.class);
            return idAnt.autoIncrement();
        });
        if ( fieldSet.isEmpty() ) return dataList;
        if ( fieldSet.size() > 1 )
            throw new IllegalArgumentException( String.format("Expecting one auto increment column, but found %d, which are %s", fieldSet.size(), fieldSet) );
        //一个列
        Method setterMethod = null;
        for (String field : fieldSet) {
            setterMethod = OrmHelper.getSetterMethodByField(field, cls);
        }
        if (setterMethod == null) {
            LOG.warn("No setter method for attribution %s", fieldSet);
            return dataList;
        }

        int columnCount = resultSet.getMetaData().getColumnCount();
        if (columnCount == 0) return dataList;
        int index = 0;
        TypeHandler typeHandler = configuration.getTypeHandler();
        try {
            do {
                Object targetObj = dataList.get(index++),
                        value = typeHandler.jdbcToJavaBeanType(resultSet, 1, setterMethod.getParameterTypes()[0]);
                setterMethod.invoke(targetObj, value);
            } while ( resultSet.next() );
        } catch (Exception ex){
            LOG.error("Setting auto increment column value from database Error", ex);
            throw new BuildResultException("Setting auto increment column value from database Error", ex);
        }
        return dataList;
    }

    private <E> E generateRsByJavaBean(E instance, ResultSet resultSet, Map<String, Method> methodMap, Map<String, String> fieldNameMap) throws Exception {
        if (methodMap.isEmpty() || fieldNameMap.isEmpty()) return instance;

        //走javaBean逻辑
        ResultSetMetaData metaData = resultSet.getMetaData();
        final int columnCount = metaData.getColumnCount();
        int i = 0;
        TypeHandler typeHandler = configuration.getTypeHandler();
        while (i++ < columnCount){
            String columnName = metaData.getColumnName(i);
            Method method = methodMap.get( fieldNameMap.get(columnName) );
            if (method == null){
                continue;
            }
            Object javaObj = typeHandler.jdbcToJavaBeanType(resultSet, i, method.getParameterTypes()[0]);
            method.invoke(instance, javaObj);
        }
        return instance;
    }

    private Map<String, Method> getFieldSetterMethodMap(Class clazz){
        Method[] methods = clazz.getMethods();
        Map<String, Method> methodMap = new HashMap<>();

        for (Method method : methods) {
            String tempFieldName = OrmHelper.getFieldBySetterMethod(method);
            if ( Str.isNull(tempFieldName) ){
                continue;
            }
            methodMap.put(tempFieldName, method);
        }
        return methodMap;
    }
}